Create Module
ulsestorm_ToDoCrud module
Generating Crud Files
As previously mentioned, we’re going to create a model for a “To Do” item in our imaginary productivity application. We’ll want this model to have two main fields — the text of the to do item, and a date completed field.
To generate the base files needed for this module, run the following generate_crud_model
command
$ pestle.phar generate_crud_model Pulsestorm_ToDoCrud TodoItem
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Model/TodoItemInterface.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Model/ResourceModel/TodoItem/Collection.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Model/ResourceModel/TodoItem.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Model/TodoItem.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Setup/InstallSchema.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Setup/InstallData.php
nce we’ve created the class files, our next step is creating the database table for our model.
To add the database table, we’ll use the Install Schema feature of Magento’s ORM. Pestle has already created a boiler plate install schema class at the following location
<?php
namespace PulsestormToDoCrudSetup;
use MagentoFrameworkSetupInstallSchemaInterface;
use MagentoFrameworkSetupSchemaSetupInterface;
use MagentoFrameworkSetupModuleContextInterface;
class InstallSchema implements InstallSchemaInterface
{
public function install(MagentoFrameworkSetupSchemaSetupInterface $installer, MagentoFrameworkSetupModuleContextInterface $context)
{
//…
//START table setup
$installer->startSetup();
$table = $installer->getConnection()->newTable(
$installer->getTable(‘pulsestorm_todocrud_todoitem’)
)->addColumn(
‘pulsestorm_todocrud_todoitem_id’,
MagentoFrameworkDBDdlTable::TYPE_INTEGER,
null,
array(
‘identity’ => true, ‘nullable’ => false, ‘primary’ => true, ‘unsigned’ => true,
),
‘Entity ID’
)->addColumn(
‘title’,
MagentoFrameworkDBDdlTable::TYPE_TEXT,
255,
array(
‘nullable’ => false,
),
‘Demo Title’
)->addColumn(
‘creation_time’,
MagentoFrameworkDBDdlTable::TYPE_TIMESTAMP,
null,
array(),
‘Creation Time’
)->addColumn(
‘update_time’,
MagentoFrameworkDBDdlTable::TYPE_TIMESTAMP,
null,
array(),
‘Modification Time’
)->addColumn(
‘is_active’,
MagentoFrameworkDBDdlTable::TYPE_SMALLINT,
null,
array(
‘nullable’ => false, ‘default’ => ‘1’,
),
‘Is Active’
);
//…
$installer->getConnection()->createTable($table);
$installer->endSetup();
}
}
?>
his code creates a PHP data structure that represents a MySQL table. Magento will automatically run this install schema class when we run bin/magento setup:upgrade
. You may see this install schema class referred to as amigration, or by its name in Magento 1 — a setup resource class.
Pestle creates a stock database table migration for you. This stock migration will create a table namedpulsestorm_todocrud_todoitem
, with the following columns
pulsestorm_todocrud_todoitem_id
title
creation_time
update_time
is_active
The first column is the table’s primary key, and will serve as the model’s ID. Pestle bases its name on the name of the table.
The second column, title
, is optional, and not used my Magento’s ORM.
The last three columns (creation_time
, update_time
, and is_active
) are fields that Magento expects to find in a model. While not strictly required, having these fields in your models is always a good idea.
Running a Magento 2 Migration
In Magento 1, the core system code automatically ran any needed migrations whenever an uncached HTTP(S) request was made. When it worked, this feature was super useful, as the simple act of adding a module to the system also automatically added its data tables. Unfortunately, when this didn’t work, it could leave the system in a half updated state that was hard to recover from.
In Magento 2, a system owner is required to run the setup:upgrade
command when they want to run a migration. Let’s give that a try with the above code in our system. Run the following
php bin/magento setup:upgrade
and you should see the pulsestorm_todocrud_todoitem
table created in your database.
mysql> show create table pulsestorm_todocrud_todoitemG;
*************************** 1. row ***************************
Table: pulsestorm_todocrud_todoitem
Create Table: CREATE TABLE `pulsestorm_todocrud_todoitem` (
`pulsestorm_todocrud_todoitem_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Entity ID',
`item_text` varchar(255) NOT NULL COMMENT 'Text of the to do item',
`date_completed` datetime DEFAULT NULL COMMENT 'Date the item was completed',
`creation_time` timestamp NULL DEFAULT NULL COMMENT 'Creation Time',
`update_time` timestamp NULL DEFAULT NULL COMMENT 'Modification Time',
`is_active` smallint(6) NOT NULL DEFAULT '1' COMMENT 'Is Active',
PRIMARY KEY (`pulsestorm_todocrud_todoitem_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='pulsestorm_todocrud_todoitem'
1 row in set (0.00 sec)
If your migration did not run, it may be because you ran setup:upgrade
before you added the install schema class. If this is the case, you’ll want to remove the information that lets Magento know thePulsestorm_ToDoCrud
module is installed in the system. You can find this information in the setup_module
table.
mysql> select * from setup_module where module = 'Pulsestorm_ToDoCrud';
+---------------------+----------------+--------------+
| module | schema_version | data_version |
+---------------------+----------------+--------------+
| Pulsestorm_ToDoCrud | 0.0.1 | 0.0.1 |
+---------------------+----------------+--------------+
1 row in set (0.00 sec)
If you delete this row
mysql> DELETE from setup_module where module = 'Pulsestorm_ToDoCrud';
and try running setup:upgrade
again, Magento should call the install
method and install your table.
The InstallSchema.php
file is meant to hold code for creating the structure of your database tables. If you wanted to install actual data into the tables you’ve created, you’d use the InstallData.php
file
app/code/Pulsestorm/ToDoCrud/Setup/InstallData.php
The InstallData.php
file is beyond the scope of this article, but take a look at some core modules to get an idea for how you’d use it
$ find vendor/magento/ -name InstallData.php
vendor/magento//magento2-base/dev/tests/api-functional/_files/Magento/TestModuleIntegrationFromConfig/Setup/InstallData.php
vendor/magento//module-authorization/Setup/InstallData.php
vendor/magento//module-bundle/Setup/InstallData.php
//...
vendor/magento//module-widget-sample-data/Setup/InstallData.php
vendor/magento//module-wishlist-sample-data/Setup/InstallData.php
A Base Magento 2 CRUD Model
Now that we’ve got a table definition installed, lets take a look at the other four files pestle created for us.
First is the base model file
#File: app/code/Pulsestorm/ToDoCrud/Model/TodoItem.php
<?php
namespace
PulsestormToDoCrudModel;
class
TodoItem
extends
MagentoFrameworkModelAbstractModel
implements
TodoItemInterface, MagentoFrameworkDataObjectIdentityInterface
{
const
CACHE_TAG =
'pulsestorm_todocrud_todoitem'
;
protected
function
_construct()
{
$this
->_init(
'PulsestormToDoCrudModelResourceModelTodoItem'
);
}
public
function
getIdentities()
{
return
[self::CACHE_TAG .
'_'
.
$this
->getId()];
}
}
MagentoFrameworkModelAbstractModel
Unlike Magento 1, all CRUD models also implement an IdentityInterface
. This interface forces model developers to define a getIdentities
method
<?php
#File: vendor/magento/framework/DataObject/IdentityInterface.php
namespace
MagentoFrameworkDataObject;
interface
IdentityInterface
{
public
function
getIdentities();
}
The last thing to make note of is the _construct
method
#File: app/code/Pulsestorm/ToDoCrud/Model/TodoItem.php
protected
function
_construct()
{
$this
->_init(
'PulsestormToDoCrudModelResourceModelTodoItem'
);
}
A model’s _construct
method is a leftover concept from Magento 1. It’s an alternative constructor. The implementation of this _construct
method is beyond the scope of this article. All you need to know is that_construct
will be called whenever a model is instantiated. Every CRUD model in Magento must use the_construct
method to call the _init
method. The _init
method accepts a single string parameter — the name of this model’s resource model.
A Magento 2 Resource Model
In Magento 2, the model class defines the methods an end-user-programmer will use to interact with a model’s data. A resource model class contains the methods that will actually fetch the information from the database. Each CRUD model in Magento 2 has a corresponding resource model class.
#File: app/code/Pulsestorm/ToDoCrud/Model/ResourceModel/TodoItem.php
<?php
namespace
PulsestormToDoCrudModelResourceModel;
class
TodoItem
extends
MagentoFrameworkModelResourceModelDbAbstractDb
{
protected
function
_construct()
{
$this
->_init(
'pulsestorm_todocrud_todoitem'
,
'pulsestorm_todocrud_todoitem_id'
);
}
}
Every CRUD resource model class extends theMagentoFrameworkModelResourceModelDbAbstractDb
class. This base class contains the basic logic for fetching information from a single database table.
For a basic model like ours, the only thing a resource model must do is call the _init
method from _construct
. The _init
method for a resource model accepts two arguments. The first is the name of the database table (pulsestorm_todocrud_todoitem
), and the second is the ID column for the model (pulsestorm_todocrud_todoitem_id
).
While it’s beyond the scope of this article, Magento 2’s active record implementation contains no method for linking tables via primary keys. How to use multiple database tables is up to each individual module developer, and a resource model will typically contain the SQL generating methods needed to fetch information from related tables.
A Magento 2 Collection Model
With a model and resource model, you have everything you need to fetch and save individual models into the database. However, there are times where you’ll want to fetch multiple models of a particular type. To solve this problem, every CRUD model in Magento 2 has a corresponding resource model collection. A collection collectsindividual models. It’s considered a resource model since it builds the SQL code necessary to pull information from a database table.
#File: app/code/Pulsestorm/ToDoCrud/Model/ResourceModel/TodoItem/Collection.php
<?php
namespace
PulsestormToDoCrudModelResourceModelTodoItem;
class
Collection
extends
MagentoFrameworkModelResourceModelDbCollectionAbstractCollection
{
protected
function
_construct()
{
$this
->_init(
'PulsestormToDoCrudModelTodoItem'
,
'PulsestormToDoCrudModelResourceModelTodoItem'
);
}
}
All collections in Magento 2 extend the baseMagentoFrameworkModelResourceModelDbCollectionAbstractCollection
collection class. Like a model and resource model, a collection resource model must call the _init
method. A collection resource model’s _init
method accepts two arguments. The first is the model that this collection collects. The second is that collected model’s resource model.
Using a Crud Model
Now that we’ve explored what each of the 6 files created by pestle’s generate_crud_model
do, we’re ready to get into using the CRUD models.
First, let’s make sure the view we setup earlier is working. Open the following file and add the following var_dump
to the _prepareLayout
method.
http://magento.example.com/pulsestorm_todocrud
and you should see your I am Here
text on a white screen.
The first thing we’re going to do is instantiate a new To Do Item model, set its text, and then save it. In Magento 1, we’d use code that looked something like this
- We’ll use automatic constructor dependency injection to …
- … inject a factory object, and then use the factory object to instantiate our CRUD model
If you’re not familiar with automatic constructor dependency injection, you’ll want to work your way through ourMagento 2 object manager series. If you’re not familiar with Factory objects — that’s because we haven’t covered them yet!
Magento 2 Factory Objects
In object oriented programming, a factory method is a method that’s used to instantiate an object. Factory methods exist to ensure system developers have control over how a particular object is instantiated, and how its arguments are passed in. There’s a certain school of though that thinks direct use of the new
keyword in programming
1
|
$object = new Foo; |
is an anti-pattern, as directly instantiating an object creates a hard coded dependency in a method. Factory methods give the system owner the ability to control which objects are actually returned in a given context.
A factory object serves a similar purpose. In Magento 2, each CRUD model has a corresponding factory class. All factory class names are the name of the model class, appended with the word “Factory”. So, since our model class is named
Pulsestorm/ToDoCrud/Model/TodoItem
this means our factory class is named
Pulsestorm/ToDoCrud/Model/TodoItemFactory
To get an instance of the factory class, replace your block class with the following.
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php
<?php
namespace
PulsestormToDoCrudBlock;
class
Main
extends
MagentoFrameworkViewElementTemplate
{
protected
$toDoFactory
;
public
function
__construct(
MagentoFrameworkViewElementTemplateContext
$context
,
PulsestormToDoCrudModelTodoItemFactory
$toDoFactory
)
{
$this
->toDoFactory =
$toDoFactory
;
parent::__construct(
$context
);
}
function
_prepareLayout()
{
var_dump(
get_class(
$this
->toDoFactory)
);
exit
;
}
}
What we’ve done here is use automatic constructor dependency injection to inject aPulsestormToDoCrudModelTodoItemFactory
factory object, and assign it to the toDoFactory
object property in the constructor method.
1
2
3
4
5
6
7
8
9
10
|
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php protected $toDoFactory ; public function __construct( MagentoFrameworkViewElementTemplateContext $context , PulsestormToDoCrudModelTodoItemFactory $toDoFactory ) { $this ->toDoFactory = $toDoFactory ; parent::__construct( $context ); } |
We also had to inject a block context object and pass that to our parent constructor. We’ll cover these context object in future articles, but if you’re curious about learning more, this quickies post is a good place to start.
In addition to injecting a factory into our block, we also added the following to our _prepareLayout
method
1
2
3
4
5
6
7
8
|
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php function _prepareLayout() { var_dump( get_class( $this ->toDoFactory) ); exit ; } |
This will dump the toDoFactory
’s class name to the screen, and is a quick sanity check that our automatic constructor dependency injection worked. Reload your page with the above in place, and you should see the following
string 'PulsestormToDoCrudModelTodoItemFactory' (length=41)
Next, replace your _prepareLayout
method with this code
1
2
3
4
5
6
7
8
9
10
|
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php function _prepareLayout() { $todo = $this ->toDoFactory->create(); $todo ->setData( 'item_text' , 'Finish my Magento article' ) ->save(); var_dump( 'Done' ); exit ; } |
This code calls the create
method of our factory. This will instantiate aPulsestormToDoCrudModelTodoItemFactory
object for us. Then, we set the item_text
property of our model, and call its save
method. Reload your page to run the above code, and then check your database table
mysql> select * from pulsestorm_todocrud_todoitemG
*************************** 1. row ***************************
pulsestorm_todocrud_todoitem_id: 1
item_text: Finish my Magento article
date_completed: NULL
creation_time: NULL
update_time: NULL
is_active: 1
1 row in set (0.00 sec)
You’ll find that Magento has saved the information you requested. If you wanted to fetch this specific model again, you’d use code that looked like the following.
1
2
3
4
5
6
7
8
9
|
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php function _prepareLayout() { $todo = $this ->toDoFactory->create(); $todo = $todo ->load(1); var_dump( $todo ->getData()); exit ; } |
Here we’ve used the factory to create our model, used the model’s load
method to load a model with the ID of 1
, and then dumped the model’s data using the various magic setter and getter methods available to a Magento 2 model.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php function _prepareLayout() { $todo = $this ->toDoFactory->create(); $todo = $todo ->load(1); var_dump( $todo ->getData()); var_dump( $todo ->getItemText()); var_dump( $todo ->getData( 'item_text' )); exit ; } |
Finally, if we wanted to use a CRUD model’s collection object, we’d use code that looked like this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php function _prepareLayout() { $todo = $this ->toDoFactory->create(); $collection = $todo ->getCollection(); foreach ( $collection as $item ) { var_dump( 'Item ID: ' . $item ->getId()); var_dump( $item ->getData()); } exit ; } |
Again, this code uses a factory object to create a CRUD model object. Then, we use the CRUD model object’sgetCollection
method to fetch the model’s collection. Then, we iterate over the items returned by the collection.
Once instantiated via a factory, Magento 2’s CRUD models behave very similarly, if not identically, to their Magento 1 counterparts. If you’re curious about Magento 1’s CRUD objects, our venerable Magento 1 for PHP MVC Developers article may be of interest, as well as the Varien Data Collections article.
Where did the Factory Come From
You may be thinking to yourself — how did Magento instantiate aPulsestorm/ToDoCrud/Model/TodoItemFactory
class if I never defined one? Factory classes are another instance of Magento 2 using code generation (first covered in our Proxy object article). Whenever Magento’s object manager encounters a class name that ends in the word Factory
, it will automatically generate the class in thevar/generation
folder if the class does not already exist. You can see your generated factory class at the following location
1
2
3
4
5
6
7
8
9
10
11
|
#File: var /generation/Pulsestorm/ToDoCrud/Model/TodoItemFactory.php <?php namespace PulsestormToDoCrudModel; /** * Factory class for @see PulsestormToDoCrudModelTodoItem */ class TodoItemFactory { //... } |